#!/usr/bin/env python

import sys
import os
import time
import json
import subprocess
import threading
import traceback
import tempfile
import copy

# we expect to be running from the dir where this script lives
scriptdir = os.path.dirname(sys.argv[0])
if scriptdir != '': os.chdir(scriptdir)

configPath = './config.py'
binaryPath = 'bs_headless.exe' if os.name == 'nt' else './bs_headless'
if not os.path.exists(binaryPath): binaryPath = './bombsquad' # fallback for testing
if not os.path.exists(binaryPath): raise Exception('unable to locate bs_headless binary')

# Server settings:
# Config values are initialized with defaults here.
# You an add your own overrides in config.py
config = {}

# Name of our server in the public parties list
config['partyName'] = 'FFA'

# If True, your party will show up in the global public party list
# Otherwise it will still be joinable via LAN or connecting by IP address
config['partyIsPublic'] = True

# UDP port to host on. Change this to work around firewalls or run multiple servers on one machine.
# 43210 is the default and the only port that will show up in the LAN browser tab.
config['port'] = 43210

# Language the server will run in.
# This is no longer terribly relevant, as all clients now see the game in their own language.
# You still may want to override this simply to keep your listing accurate however.
config['language'] = 'English'

# Max devices in the party. Note that this does *NOT* mean max players.
# Any device in the party can have more than one player on it if they have multiple controllers.
# Also, this number currently includes the server so generally make it 1 bigger than you need.
# Max-players is not currently exposed but I'll try to add that soon.
config['maxPartySize'] = 6

# Options here are 'ffa' (free-for-all) and 'teams'
# this value is only used if you do not supply a playlistCode (see below); in that case
# the default teams or free-for-all playlist gets used
config['sessionType'] = 'ffa'

# To host your own custom playlists, use the 'share' functionality in the playlist
# editor in the regular version of the game (version 1.4.100 or newer). This will give you
# a numeric code you can enter here to host that playlist
config['playlistCode'] = None

# Whether to shuffle the playlist or play its games in designated order
config['playlistShuffle'] = True

# If True, keeps team sizes equal by disallowing joining the largest team (teams mode only)
config['autoBalanceTeams'] = True

# Whether to enable telnet access on port 43250
# This allows you to run python commands on the server as it is running.
# Note: you can now also run live commands via stdin so telnet is generally unnecessary.
# BombSquad's telnet server is very simple so you may have to turn off any fancy features
# In your telnet client to get it to work.
# There is no password protection so make sure to only enable this if access to this port
# is fully trusted (behind a firewall, etc)
config['enableTelnet'] = False

# Series length in teams mode (7 means a 'best-of-7' series so a team must get 4 wins)
config['teamsSeriesLength'] = 7

# Points to win in free-for-all mode (Points are awarded per game based on performance)
config['ffaSeriesScoreToWin'] = 24

# If config.py exists, run it to apply any overrides it wants..
if os.path.isfile(configPath):
    execfile(configPath)

# launch a thread to read our stdin for commands; this lets us modify the server as it runs
inputCommands = []

# print a little spiel in interactive mode (make sure we do this before our thread reads stdin)
if sys.stdin.isatty():
    print ("bombsquad server wrapper starting up...\n"
           "tip: enter python commands via stdin to reconfigure the server on the fly:\n"
           "example: config['partyName'] = 'New Party Name'")

class InputThread(threading.Thread):
    def run(self):
        while True:
            l = sys.stdin.readline()
            inputCommands.append(l.strip())
t = InputThread()
t.daemon = True # don't let this thread's existence prevent us from dying
t.start()

restart_server = True

# the server-binary will get relaunched after this amount of time
# (combats memory leaks or other cruft that has built up)
restartMinutes = 360

# a bit of environment cleanup
del __builtins__.exit
del __builtins__.quit

# sleep for just a moment to allow initial stdin data to get through
time.sleep(0.25)

# restart indefinitely until we're told not to..
while restart_server:

    launchTime = time.time()

    # most of our config values we can feed to bombsquad as it is running (see below).
    # however certain things such a network-port need to be present in bs's config on
    # launch... so let's dump those in there first.
    if not os.path.exists('bscfg'): os.mkdir('bscfg')
    if os.path.exists('bscfg/config.json'):
        f = open('bscfg/config.json')
        bscfg = json.loads(f.read())
        f.close()
    else:
        bscfg = {}
    bscfg['Port'] = config['port']
    f = open('bscfg/config.json','w')
    f.write(json.dumps(bscfg))
    f.close()
    
    # launch our binary and grab its stdin; we'll use this to feed it commands
    result = subprocess.Popen([binaryPath,'-cfgdir','bscfg'], stdin=subprocess.PIPE)

    # set quit to True any time after launching the server to gracefully quit it at the
    # next clean opportunity (end of the current series, etc)
    config['quit'] = False
    config['quitReason'] = None
    
    # so we pass our initial config..
    configDirty = True

    # now just sleep and run commands until the server exits
    while True:

        # run commands that came in through stdin
        for c in inputCommands:
            oldConfig = copy.deepcopy(config)
            try: exec(c)
            except Exception: traceback.print_exc()
            if config != oldConfig: configDirty = True
        inputCommands = []

        # request a restart after a while
        if time.time() - launchTime > 60 * restartMinutes and not config['quit']:
            print 'restartMinutes ('+str(restartMinutes)+'m) elapsed; requesting server restart at next clean opportunity...'
            config['quit'] = True
            config['quitReason'] = 'restarting'
            configDirty = True
        
        # whenever the config changes, dump it to a json file and feed it to the running server
        if configDirty:
            f = tempfile.NamedTemporaryFile(delete=False)
            fname = f.name
            f.write(json.dumps(config))
            f.close()
            # (game handles deleting this file for us once its done with it)
            result.stdin.write('bsUtils.configServer(configFile='+repr(fname)+')\n')
            configDirty = False

        code = result.poll()
        if code is not None:
            print 'BombSquad exited with code '+str(code)
            break

        time.sleep(1)
